﻿//=============================================================================
// Project  : NIPO Component Framework
// File    : InputPlugin.cs
// Author  : Bernhard Dieber (Bernhard.Dieber@uni-klu.ac.at)
// Copyright 2010 by Bernhard Dieber
// This code is published under the Microsoft Public License (Ms-PL).  A copy
// of the license should be distributed with the code.  It can also be found
// at the project website: http://NIPO.CodePlex.com.   This notice, the
// author's name, and all copyright notices must remain intact in all
// applications, documentation, and source files.
//=============================================================================

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using NIPO.Attributes;
using NIPO.Data;

namespace NIPO.Plugins
{
    ///<summary>
    /// The InputPlugin performs input tasks like reading from file or network. Every Input plugin runs in a dedicated thread.<br />
    ///</summary>
    /// <remarks>
    /// To implement a new InputPlugin inherit from <see cref="InputPlugin"/>. Implement the necessary methods.<br />
    /// The <see cref="InputPlugin.Run"/> should contain your Input logic. Perform the necessary operations until you are finished or until <see cref="InputPlugin.Abort"/> is <c>true</c>.<br />
    /// Pass on the collected input data in sensful chunks. To pass on data call <see cref="InputPlugin.RaiseNewData"/> and pass on your data
    /// in a suitable subclass of <see cref="NIPO.Data.DataTransferObject"/> (in most cases NIPO.Data.GenericDTO will be sufficient).<br />
    /// Do not forget to catch ThreadInterruptedException and ThreadAbortException since <see cref="System.Threading.Thread.Interrupt"/> and <see cref="System.Threading.Thread.Abort()"/> may be called .<br />
    /// </remarks>
    public abstract class InputPlugin : IPOPlugin
    {
        private bool _abort;
        private Thread _worker;

        /// <summary>
        /// Occurs when the InputPlugin has new data to pass on
        /// </summary>
        [DataEvent]
        public event NIPO.Data.NIPOEventHandler NewData;

        /// <summary>
        /// Gets a value indicating whether this <see cref="InputPlugin"/> should stop its task.
        /// </summary>
        /// <value><c>true</c> if abort; otherwise, <c>false</c>.</value>
        public bool Abort
        {
            get
            {
                return _abort;
            }

        }
        /// <summary>
        /// The concrete implementation of this method provides the core input functionality of an InputPlugin. This method is always run in a dedicated Thread
        /// </summary>
        public abstract void Run();
        /// <summary>
        /// Starts the InputPlugin
        /// </summary>
        public void Start()
        {
            _abort = false;
            _worker = new Thread(Run);
            _worker.Start();
        }

        /// <summary>
        /// Stops this instance.<br />
        /// Multiple steps are taken to safely stop an instance:<br />
        /// <list type="number">
        /// <item>Set Abort to true and wait 2 seconds for the plugin to terminate</item>
        /// <item>Interrupt the plugin in case it is blocked in synchonous IO, wait another 10 seconds</item>
        /// <item>Abort the Thread, wait 5 seconds for join</item>
        /// </list>
        /// </summary>
        public void Stop()
        {
            try
            {
                _abort = true;
                if (!_worker.Join(2000))
                {
                    Log.WriteDebugFormat("InputPlugIn: Joining {0}", ID);
                    _worker.Interrupt();
                    if (!_worker.Join(10000))
                    {
                        _worker.Abort();
                        if (!_worker.Join(5000))
                            Log.WriteDebugFormat("Unable to stop InputPlugIn {0}", ID);
                    }
                }
                Log.WriteInfoFormat("Stopped {0}", this.ID);
            }
            catch (Exception ex)
            {
                Log.WriteErrorFormat("InputPlugIn: Error stopping PlugIn {0}: {2} {1}\n {3}", ID, ex.Message, ex.GetType().FullName, ex.StackTrace);
            }
        }

        /// <summary>
        /// Can be called by the InputPlugin to pass on a data chunk to the processing plugin.
        /// </summary>
        /// <param name="dto">A data object to pass to the next processing chain element</param>
        protected void RaiseNewData(DataTransferObject dto)
        {
            if (NewData == null) return;
            //Log.WriteInfo("NewData");
            if (ConfigurationObject == null || ConfigurationObject.AsynchronousInvoke)
            {

                ThreadPool.QueueUserWorkItem(delegate
                {
                    NewData.Invoke(this, new NIPOEventArgs(dto, ID));
                });
            }
            else
                NewData(this, new NIPOEventArgs(dto, ID));
        }

        internal override void InternalReset()
        {
            NewData = null;
        }

    }
}